package com.baroxtech.anoterecorder;

import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.midi.MidiDevice;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiDeviceStatus;
import android.media.midi.MidiManager;
import android.media.midi.MidiOutputPort;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;


import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public class AMIDIDevManager {
    public static final class EventType {
        public static final int START_CONNECT = 0; // 设备开始连接
        public static final int CONNECT_FAILED = 1;// 设备连接失败
        public static final int CONNECTED = 2;// 设备成功连接
        public static final int DISCONNECTED = 3;// 设备成功连接
        public static final int MIDI_COMMANDS = 4; // 收到数据
        public static final int DEVICE_REMOVED = 5; // 设备移除
    }

    public interface EventHandler {
        void onEvent(int eventType, final List<AMIDICommand> commands, final AMIDIDev dev);
    }

    private final static String TAG = "AMIDIDevManager";
    private static AMIDIDevManager instance;
    private Application context;
    private boolean running;
    private boolean connecting; // 正在连接中
    private AMIDIDev connectedDevice;
    private MidiOutputPort connectedOutputPort;
    private LinkedList<EventHandler> handlers;
    private MidiManager midiManager;
    private BroadcastReceiver bluetoothSwitchStateReceiver;
    private MidiManager.DeviceCallback deviceCallback;
    private Handler mainHandler;

    private AMIDIDevManager() {
        handlers = new LinkedList<>();
        running = false;
    }

    public static AMIDIDevManager getInstance() {
        if (AMIDIDevManager.instance == null) {
            AMIDIDevManager.instance = new AMIDIDevManager();
        }
        return AMIDIDevManager.instance;
    }

    public void init(Application context) {
        if (this.context != null) return;
        this.context = context;
        midiManager = (MidiManager) context.getSystemService(Context.MIDI_SERVICE);
        // 配置蓝牙开关关闭的监听
        bluetoothSwitchStateReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (BluetoothAdapter.STATE_OFF == intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)) {
                    // 蓝牙关闭的时候判定是否连接了蓝牙设备，然后断开它
                    disconnect(connectedDevice, true);
                }
            }
        };
        mainHandler = new Handler(Looper.getMainLooper());
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        context.registerReceiver(bluetoothSwitchStateReceiver, filter);
    }

    public void pause() {
        if (!running) return;
        running = false;
        disconnect(connectedDevice, false);
    }

    public void resume() {
        if (running) return;
        running = true;
        if (deviceCallback == null) {
            deviceCallback = new MidiManager.DeviceCallback() {
                @Override
                public void onDeviceAdded(MidiDeviceInfo info) {
                    // usb midi 设备添加
                    if (info.getType() != MidiDeviceInfo.TYPE_USB) return;
                    connect(new AMIDIDev(info));
                }

                @Override
                public void onDeviceRemoved(MidiDeviceInfo info) {
                    // 判定类型，是哪一种设备被移除
                    disconnect(new AMIDIDev(info), true);
                }

                // Update port open counts so user knows if the device is in use.
                @Override
                public void onDeviceStatusChanged(MidiDeviceStatus status) {
                    Log.d(TAG, "" + status);
                }
            };
            midiManager.registerDeviceCallback(deviceCallback, mainHandler);
        }
        if (connectedDevice != null) {
            connect(connectedDevice);
        } else {
            connectFirstUSBDev();
        }
    }

    public boolean isRunning() {
        return running;
    }

    public boolean isOutPortConnected() {
        return connectedOutputPort != null;
    }

    public void addEventHandler(EventHandler handler) {
        if (!handlers.contains(handler)) {
            handlers.add(handler);
        }
    }

    public void removeEventHandler(EventHandler handler) {
        handlers.remove(handler);
    }

    private void handleEvent(int eventType, final List<AMIDICommand> commands, final AMIDIDev dev) {
        for (EventHandler handler : handlers) {
            handler.onEvent(eventType, commands, dev);
        }
    }

    private boolean openOutPort(MidiDevice device, final AMIDIDev dev) {
        MidiDeviceInfo info = device.getInfo();
        if (info.getOutputPortCount() == 0) return false;
        // 遍历端口，打开第一个可用
        for (MidiDeviceInfo.PortInfo portInfo : info.getPorts()) {
            if (portInfo.getType() == MidiDeviceInfo.PortInfo.TYPE_OUTPUT) {
                // 打开端口
                connectedOutputPort = device.openOutputPort(portInfo.getPortNumber());
                if (connectedOutputPort != null) break;
            }
        }
        // 判断是否打开成功
        if (connectedOutputPort == null) return false;
        connectedDevice = dev;
        connectedDevice.device = device;
        // 打开成功了，处理 midi 数据
        connectedOutputPort.connect(new AMIDICommandReceiver(new AMIDICommandReceiver.CommandsHandler() {
            @Override
            public void onCommands(final List<AMIDICommand> commands) {
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        handleEvent(EventType.MIDI_COMMANDS, commands, connectedDevice);
                    }
                });
            }
        }));
        return true;
    }

    private void connectFirstUSBDev() {
        for (MidiDeviceInfo info : midiManager.getDevices()) {
            if (info.getType() == MidiDeviceInfo.TYPE_USB && info.getOutputPortCount() > 0 && connect(new AMIDIDev(info))) {
                return;
            }
        }
    }

    boolean connect(final AMIDIDev dev) {
        if (connectedOutputPort != null || dev == null || !running) return false;
        handleEvent(EventType.START_CONNECT, null, dev);

        if (connecting) {
            handleEvent(EventType.CONNECT_FAILED, null, dev);
            return false;
        }
        connecting = true;
        // 发送开始连接的信号
        MidiManager.OnDeviceOpenedListener listener = new MidiManager.OnDeviceOpenedListener() {
            @Override
            public void onDeviceOpened(MidiDevice midiDevice) {
                // 如果 midiDevice 是 null 表示打开失败 (in main thread)
                int eventType = EventType.CONNECT_FAILED;
                if (midiDevice != null && openOutPort(midiDevice, dev)) {
                    eventType = EventType.CONNECTED;
                }
                connecting = false;
                handleEvent(eventType, null, dev);
                if (eventType == EventType.CONNECT_FAILED) {
                    try {
                        midiDevice.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Handler handler = new Handler(Looper.getMainLooper());
        if (dev.isUsb()) {
            midiManager.openDevice(dev.devInfo, listener, handler);
        } else {
            midiManager.openBluetoothDevice(dev.bleDev, listener, handler);
        }
        return true;
    }

    void disconnect(AMIDIDev dev, boolean isFromSystem) {
        if (connectedOutputPort == null) return;
        // 判定是否同一个设备
        if (dev != null && !connectedDevice.isEqual(dev)) return;
        // 是同一个设备执行断开操作
        try {
            connectedOutputPort.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        connectedOutputPort = null;
        // close device
        try {
            connectedDevice.device.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        handleEvent(EventType.DISCONNECTED, null, connectedDevice);
        // 判定设备是否真的已经断开
        if (isFromSystem) {
            handleEvent(EventType.DEVICE_REMOVED, null, connectedDevice);
            connectedDevice = null;
        }
        // 如果还在运行中，尝试连接 usb 设备
        connectFirstUSBDev();
    }

    AMIDIDev getConnectedDevice() {
        return (connectedOutputPort == null) ? null : connectedDevice;
    }

    boolean isConnected(final AMIDIDev dev) {
        AMIDIDev cdev = getConnectedDevice();
        return cdev != null && cdev.isEqual(dev);
    }
}
